package websocket;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.logging.Level;
import java.util.logging.Logger;
import websocket.Framedata.ClosingReason;

public class WebSocketConnection
{
    private Socket socket;
    private Handshake handshake;
    private ReceivingThread receivingThread;
    private WebSocketListener listener = null;
    private State state = State.NOT_YET_CONNECTED;

    private final static Logger LOGGER = Logger.getLogger(WebSocketConnection.class
        .getName());
    
    public enum State
    {
        NOT_YET_CONNECTED, CONNECTING, OPEN, CLOSING, CLOSED
    }
    
    public WebSocketConnection(Socket client, HttpRequest handshakeRequest) throws IOException
    {
        if (client == null || handshakeRequest == null)
        {
            throw new IllegalArgumentException("Client socket or HttpRequest is null");
        }
        socket = client;
        
        LOGGER.log(Level.FINER, "Performing handshake with client {0}:{1}",
                   new Object[] {client.getInetAddress(), client.getPort()});
        
        handshake = new Handshake(handshakeRequest);
        
        state = State.CONNECTING;
        handshake.doHandshake(client);
        state = State.OPEN;

        receivingThread = new ReceivingThread(this, socket);
        receivingThread.start();
    }
    
    public void setListener(WebSocketListener listener)
    {
        this.listener = listener;
    }
    
    public void sendMessage(String message)
    {
        // Obtain frames for the message and start sending thread with it
        SendingThread thread = new SendingThread(this, socket,
                Framedata.convertMessageToFrames(message));
        thread.start();
    }
    
    public void sendMessage(Framedata[] frames)
    {
         new SendingThread(this, socket, frames).start();
    }

    void onMessage(String textData)
    {
        LOGGER.fine("Text message was received");
        if (listener != null)
        {
            listener.onMessage(textData);
        }
    }

    void onCloseFrame(ClosingReason closingReason)
    {
        LOGGER.fine("Close frame was received");
        
        // Check the state - if it is close request or close response
        if (state != State.CLOSING)
        {
            // The client sent a close request
            state = State.CLOSING;
            LOGGER.log(Level.FINER, "Closing reason: {0}", closingReason);
            
            Framedata response = Framedata.generateCloseFrame();
            // Send close response and close connection
            SendingThread thread = new SendingThread(this, socket, new Framedata[] {response}, true);
            thread.start();
        }
        else
        {
            // We have sent the close request and now the we have received close
            // response frm the client
            try
            {
                socket.close();
            }
            catch (IOException ex)
            {
                // Do nothing, we wanted to close connection anyway
            }
            
            setClosedState();
        }
    }

    void onPingFrame()
    {
        LOGGER.fine("Ping frame was received");
        
        // Send back pong frame
        Framedata response = Framedata.generatePongFrame();
        SendingThread thread = new SendingThread(this, socket, new Framedata[] {response});
        thread.start();
    }

    void onPongFrame()
    {
        LOGGER.fine("Pong frame was received");
        // TODO [If you need] Implement handling Pong frames if you need it
    }

    void onNotSupportedData()
    {
        LOGGER.warning("Not supported data error was raised");
        
        // Close connection with 'unsupported data' reason
        Framedata response = Framedata.generateCloseFrame(ClosingReason.UNSUPPORTED_DATA);
        SendingThread thread = new SendingThread(this, socket, new Framedata[] {response});
        thread.start();
        state = State.CLOSING;
        // ClosingReason.UNSUPPORTED_DATA;
    }

    void onMessageTooBig()
    {
        LOGGER.warning("Too big message error was raised");
        
        // Close connection with 'message too big' reason
        Framedata response = Framedata.generateCloseFrame(ClosingReason.MESSAGE_TOO_BIG);
        SendingThread thread = new SendingThread(this, socket, new Framedata[] {response});
        thread.start();
        state = State.CLOSING;
    }

    void onProtocolError(String errorMsg)
    {
        LOGGER.log(Level.WARNING, "Protocol error was raised: {0}", errorMsg);
        
        // Close connection with 'protocol error' reason
        Framedata response = Framedata.generateCloseFrame(ClosingReason.PROTOCOL_ERROR);
        SendingThread thread = new SendingThread(this, socket, new Framedata[] {response});
        thread.start();
        state = State.CLOSING;
    }

    void onBrokenConnection(IOException ex)
    {
        LOGGER.log(Level.INFO, "Connection is broken. IOException was thrown: {0}", ex.getMessage());
        LOGGER.fine(ex.toString());
        
        if (listener != null)
        {
            listener.onBrokenConnection();
        }
    }
    
    void setClosedState()
    {
        state = State.CLOSED;
        if (listener != null)
        {
            listener.onClosed();
        }
    }
    
    public int getPort()
    {
        return socket.getPort();
    }
    
    public String getInetAddress()
    {
        return socket.getInetAddress().getHostAddress();
    }
    
    public State getState()
    {
        return state;
    }
    
    
}
